/*
The MIT License (MIT)

Copyright (c) 2013 Mike Dapiran, Brian May, Richard Pospesel, and Bert Wierenga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once

#include "hhdMathConfig.h"

namespace hhd
{
	class PrintStream;

	/**
	* Vector3 class; This is considered a primitive data type in the engine. This class is a Cartesian 3-dimensional vector. The scaler components
	* are labelled x, y, and z. A large number of operators are defined for convenience. 
	*/
	class Vector3
	{
	public:
		/**
		* default constructor; creates a zero vector.
		*/
		Vector3();

		/**
		* piecewise constructor; creates a vector from the three parameters
		*/
		Vector3(float in_x, float in_y, float in_z);

		/**
		* copy constructor; creates this a vector from a deep copy of a source vector.
		*
		* @param other The source vector to copy from.
		*/
		Vector3(const Vector3& other);

		/**
		* constructor assumes an array of 3 floats
		*
		*/
		Vector3(const float* other);

		/**
		* assignment operator; deep copies to this a vector from a source vector.
		*
		* @param in_other The source vector to copy from.
		*/
		Vector3& operator=(const Vector3& in_other);


		/**
		* friend multiplication operator.
		*
		* @return returns a Vector3 of the format (left.x*right, left.y*right, left.z*right).
		*/
		friend Vector3 operator*(const Vector3& left, float right);

		/**
		* friend multiplication operator.
		*
		* @return returns a Vector3 of the format (left.x*right, left.y*right, left.z*right).
		*/
		friend Vector3 operator*(float left, const Vector3& right);

		/**
		* float cast operator;
		*
		* @return Returns a float* to the address of this->x in memory. x,y,z are stored
		* contiguously in memory.
		*/
		operator float*();

		/**
		* const float cast operator;
		*
		* @return Returns a const float* to the address of this->x in memory. x,y,z are stored
		* contiguously in memory.
		*/
		operator const float*() const;

		/**
		* multiplication/assignment operator; multiplies each scalar component of the vector by
		* a scalar. 
		*/
		Vector3& operator*=(float f);

		/**
		* subtraction operator.
		*
		* @return Returns an instance of Vector3 from defined as (this->x-right.x, this->y-right.y, this->z-right.z).
		*/
		Vector3 operator-(const Vector3& right) const;

		/**
		* addition operator.
		*
		* @return Returns an instance of Vector3 from defined as (this->x+right.x, this->y+right.y, this->z+right.z).
		*/
		Vector3 operator+(const Vector3& right) const;

		/**
		* addition/assignment operator; adds a Vector3 to this Vector3.
		*
		* @param right The Vector3 to add to this one.
		* @return returns *this.
		*/ 
		Vector3& operator+=(const Vector3& right);

		/**
		* addition/assignment operator; subtracts a Vector3 to this Vector3.
		*
		* @param right The Vector3 to add to this one.
		* @return returns *this.
		*/ 
		Vector3& operator-=(const Vector3& right);

		/**
		* equivalence operator.
		*/
		bool operator==(const Vector3& right) const;

		/**
		* not equivalent operator.
		*/
		bool operator!=(const Vector3& right) const;
			
		friend PrintStream& operator<<(PrintStream& in_stream, const Vector3& in_v);

		/**
		* the zero Vector3.
		*/
		const static Vector3& zero;

		/**
		* cross product method.
		*
		* @param left The first operand.
		* @param right The second operand.
		*
		* @return Returns the cross product (left cross right) of left and right.
		*/
		static Vector3 cross(const Vector3& left, const Vector3& right);

		/**
		* dot product method.
		*
		* @param left The first operand.
		* @param right The second operand.
		*
		* @return Returns the dot product of left and right.
		*/
		static float dot(const Vector3& left, const Vector3& right);

		/**
		* box product method; this is defined as (a x b) . c.
		*
		* @param a The first operand.
		* @param b The second operand.
		* @param c The third operand.
		*
		* @return Returns the box product ( (a cross b) dot c ) of a, b and c.
		*/
		static float box(const Vector3& a, const Vector3& b, const Vector3& c);

		static float distanceSquared(const Vector3& left, const Vector3& right);
		static float distance(const Vector3& left, const Vector3& right);

		const static Vector3 forward;
		const static Vector3 backward;
		const static Vector3 up;
		const static Vector3 down;
		const static Vector3 right;
		const static Vector3 left;
		const static Vector3 infinity;

		Vector3& normalize();
		Vector3 normalized() const;

		float getMagnitude()  const;
		float squareMagnitude() const;

		Vector3 projectOnto( const Vector3& in_onto );

		static Vector3 lerp( const Vector3& in_start, const Vector3& in_end, float t );
		static Vector3 piecewiseMul( const Vector3& in_one, const Vector3& in_two );
			
		float x;
		float y;
		float z;
		/**
		* Sets the components to the passed in floats
		*/
		inline void set(float in_x, float in_y, float in_z)
		{
			x = in_x;
			y = in_y;
			z = in_z;
		}
		/*
		Check to see if the passed in vectors are parallel (within a certain angle)
		*/
		static bool parallel(const Vector3&, const Vector3&, float angle, bool radians=false);
	};
}
